package com.jxtc.enterprise.admin.controller;

import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.ExcelWriter;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.jxtc.enterprise.admin.dto.*;
import com.jxtc.enterprise.admin.vo.AdminOrderDetailVO;
import com.jxtc.enterprise.admin.vo.AdminOrderWithTenantNameVO;
import com.jxtc.enterprise.common.aspectj.TokenRequired;
import com.jxtc.enterprise.common.entity.OrderDetail;
import com.jxtc.enterprise.common.mapper.OrderDetailMapper;
import com.jxtc.enterprise.common.mapper.OrderMapper;
import com.jxtc.enterprise.common.utils.Result;
import com.jxtc.enterprise.common.utils.ResultGenerator;
import com.jxtc.enterprise.common.vo.PaginationResponseVO;
import com.jxtc.enterprise.hr.dto.OrderDetailDTO;
import com.jxtc.enterprise.hr.dto.OrderDetailStoreFoodDTO;
import com.jxtc.enterprise.merchant.dto.order_manage.StoreInfoForOrderDTO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.Sheet;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.time.LocalDate;
import java.util.*;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * 管理员端，订单管理页面 的接口
 */
@RestController
@RequestMapping("/jxtc/enterprise/admin/orderManage")
@RequiredArgsConstructor
@Slf4j
public class AdminOrderManageController {

    private final OrderMapper orderMapper;
    private final OrderDetailMapper orderDetailMapper;


    @Operation(summary = "1.6.1 根据查询条件(公司名称，查询时间)分页查询订单", tags = {"1.6 管理员订单管理"}, operationId = "1.6.1")
    @TokenRequired(role = TokenRequired.ROLE_ADMIN)
    @PostMapping("/queryOrderByPage")
    public Result<PaginationResponseVO<AdminOrderWithTenantNameVO>> queryOrderByPageForAdmin(@RequestBody AdminPageOrderDTO dto) {
        log.info("/queryOrderByPage, request: {}", JSONObject.toJSONString(dto));

        String startDateTime = dto.getStartDateTime();
        String endDateTime = dto.getEndDateTime();

        PaginationResponseVO<AdminOrderWithTenantNameVO> vo = pageOrderForAdmin(dto.getKeyword(), dto.getTenantName(),
                startDateTime, endDateTime, dto.getCurrent(), dto.getSize());

        log.info("/queryOrderByPage, response: {}", JSONObject.toJSONString(vo));
        return ResultGenerator.genSuccessResult(vo);
    }


    @ApiResponse(responseCode = "200", description = "code 为 200 时，data 字段为包含订单详情列表的分页信息")
    @Operation(summary = "1.6.2 根据公司 ID 和日期查询订单详情列表", tags = {"1.6 管理员订单管理"}, operationId = "1.6.2")
    @TokenRequired(role = TokenRequired.ROLE_ADMIN)
    @PostMapping("/queryOrderDetail")
    public Result<PaginationResponseVO<AdminOrderDetailVO>> queryOrderDetailForAdmin(@RequestBody AdminQueryOrderDetailDTO dto) {
        log.info("/queryOrderDetail, companyId: {}, date: {}", dto.getCompanyId(), dto.getDate());
        PaginationResponseVO<AdminOrderDetailVO> vo = pageOrderDetailByCompanyIdAndDate(dto.getCompanyId(), dto.getDate(), dto.getPage(), dto.getSize());
        return ResultGenerator.genSuccessResult(vo);
    }


    @ApiResponse(responseCode = "200", description = "导出成功")
    @Operation(summary = "1.6.3 根据公司和日期信息列表将订单信息导出成 Excel", tags = {"1.6 管理员订单管理"}, operationId = "1.6.3")
    @TokenRequired(role = TokenRequired.ROLE_ADMIN)
    @PostMapping("/export2Excel")
    public void export2ExcelForAdmin(HttpServletResponse response, @Valid @RequestBody AdminOrderExportForAdminDTO dto) {
        log.info("/export2Excel, {}", dto);

        List<CompanyWithDate> rows = dto.getRows();

        if (dto.getSelectAll() != null && dto.getSelectAll()) {
            String companyName = dto.getKeyword() == null ? "" : dto.getKeyword();
            PaginationResponseVO<AdminOrderWithTenantNameVO> vo = pageOrderForAdmin(companyName, dto.getTenantName(), null, null, 1, Integer.MAX_VALUE);

            rows = vo.getRecords().stream()
                    .map(record -> new CompanyWithDate(record.getCompanyId(), record.getCompanyName(), record.getDate()))
                    .collect(Collectors.toList());
        }

        try (ExcelWriter writer = ExcelUtil.getWriter(true)) {
            boolean firstSheet = true;
            List<AdminOrderDetailVO> summaryOrderDetailList = new ArrayList<>();
            for (CompanyWithDate row : rows) {
                PaginationResponseVO<AdminOrderDetailVO> orderDetailVO = pageOrderDetailByCompanyIdAndDate(row.getCompanyId(), row.getDate(), 1, Integer.MAX_VALUE);
                List<Map<String, Object>> data = new ArrayList<>();
                summaryOrderDetailList.addAll(orderDetailVO.getRecords());

                // 【1103新增功能】 先某个公司的员工的所有订单的id，查询出该公司员工订单的 店铺相关信息，用来作为导出excel文件中，新增的【店铺名】，【支付给商家的费用】 两列的值
                Map<String, StoreInfoForOrderDTO> orderIdToVoMap = evalOrderIdToMerchantInfoMap(orderDetailVO.getRecords());

                int idx = 1;
                for (AdminOrderDetailVO orderDetail : orderDetailVO.getRecords()) {
                    // 【1103新增功能】 从map中取出各个订单支付信息中，需要为店铺支付的费用相关的DTO
                    StoreInfoForOrderDTO storeInfoForOrderDto = orderIdToVoMap.getOrDefault(orderDetail.getOrderId(), new StoreInfoForOrderDTO());
                    data.add(createRow(idx++, orderDetail.getUsername(), orderDetail.getDepartment(), orderDetail.getOrderName(), orderDetail.getPrice(),
                            storeInfoForOrderDto.getStoreName(), storeInfoForOrderDto.getPayForMerchant(), orderDetail.getOrderTime(), orderDetail.getNote()));
                }

                // 将前端传递过来的 '2024-07-22 (午餐 15:00 ~ 14:00)' 格式的时间，分离出日期
                String[] dateAndMealFlag = row.getDate().split(" ");
                if (dateAndMealFlag.length < 2) {
                    log.error("companyId: {}, rawDate: {}", row.getCompanyId(), row.getDate());
                    throw new IllegalArgumentException("时间格式不正确，请联系管理员排查");
                }
                String date = dateAndMealFlag[0];
                String meal = dateAndMealFlag[1].replace("(", "");
                // 将前端传递过来的 '2024-07-22 (午餐 15:00 ~ 14:00)' 解析为类似 15001400 格式，拼接在 sheet 名称后
                // 仅靠 date 和 meal 无法避免以下格式的数据导致 sheet 页名称重复问题
                // 2024-07-31 (午餐 19:00 ~ 10:00)
                // 2024-07-31 (午餐 19:00 ~ 10:30)
                // 避免类似这种数据导致导出的 excel 中缺少 sheet 页
                // 注：excel 中 sheet 页名称中不能包含 ":"
                String timeRange = parseTimeRange(row.getDate());

                String sheetName = row.getCompanyName() + "-" + date + meal + timeRange;
                addSheet(writer, sheetName, data, firstSheet);
                firstSheet = false;
            }
            // 将所有 sheet 页的数据汇总到一个 sheet 页中，并按下单时间降序排列
            addSummarySheet(summaryOrderDetailList, writer);

            String filename = System.currentTimeMillis() + ".xlsx";

            response.setContentType("application/vnd.ms-excel;charset=utf-8");
            response.setHeader("Content-Disposition", "attachment;filename=" + filename);
            writer.flush(response.getOutputStream(), true);
        } catch (Exception e) {
            e.printStackTrace();
            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            throw new RuntimeException("Error while generating Excel file", e);
        }
    }

    /**
     * 将类似 '2024-07-22 (午餐 15:00 ~ 14:00)' 格式的数据解析为 15001400 这种格式的字符串
     */
    private String parseTimeRange(String input) {
        // 定义匹配时间的正则表达式
        Pattern pattern = Pattern.compile("(\\d{2}:\\d{2}) ~ (\\d{2}:\\d{2})");
        Matcher matcher = pattern.matcher(input);

        if (matcher.find()) {
            // 提取开始时间和结束时间
            String startTime = matcher.group(1).replace(":", "");
            String endTime = matcher.group(2).replace(":", "");
            // 合并时间部分
            return startTime + endTime;
        }
        return "";
    }

    private void addSummarySheet(List<AdminOrderDetailVO> summaryOrderDetailList, ExcelWriter writer) {
        // 按照下单时间降序排序
        List<AdminOrderDetailVO> sortedByTime = summaryOrderDetailList.stream()
                .sorted(Comparator.comparing(AdminOrderDetailVO::getOrderTime, Comparator.reverseOrder()))
                .collect(Collectors.toList());
        // 【1103新增功能】 先某个公司的员工的所有订单的id，查询出该公司员工订单的 店铺相关信息，用来作为导出excel文件中，新增的【店铺名】，【支付给商家的费用】 两列的值
        Map<String, StoreInfoForOrderDTO> orderIdToVoMap = evalOrderIdToMerchantInfoMap(summaryOrderDetailList);
        List<Map<String, Object>> data = new ArrayList<>();
        int idx = 1;
        for (AdminOrderDetailVO orderDetail : sortedByTime) {
            // 【1103新增功能】 从map中取出各个订单支付信息中，需要为店铺支付的费用相关的DTO
            StoreInfoForOrderDTO storeInfoForOrderDto = orderIdToVoMap.getOrDefault(orderDetail.getOrderId(), new StoreInfoForOrderDTO());
            data.add(createRow(idx++, orderDetail.getUsername(), orderDetail.getDepartment(), orderDetail.getOrderName(),
                    orderDetail.getPrice(),
                    storeInfoForOrderDto.getStoreName(), storeInfoForOrderDto.getPayForMerchant(),
                    orderDetail.getOrderTime(), orderDetail.getNote()));
        }
        // 添加一个新的 sheet 页，并设置为第一个 sheet 页
        Sheet summarySheet = writer.getWorkbook().createSheet("汇总");
        writer.getWorkbook().setSheetOrder("汇总", 0);

        // 设置“餐品”这一列的宽度，索引从 0 开始
        int dishNameColumnIndex = 3;
        summarySheet.setColumnWidth(dishNameColumnIndex, 40 * 256);

        // 设置“下单时间”这一列的宽度
        int orderTimeColumnIndex = 5;
        summarySheet.setColumnWidth(orderTimeColumnIndex, 20 * 256);

        // 写入数据
        writer.setSheet("汇总");
        writer.write(data, true);

        // 默认打开 “汇总” 这个 sheet 页
        writer.getWorkbook().setActiveSheet(0);
    }

    private PaginationResponseVO<AdminOrderDetailVO> pageOrderDetailByCompanyIdAndDate(String companyId, String rawDate, int current, int size) {
        Page<OrderDetailDTO> page = new Page<>(current, size);
        // 将前端传递过来的 '2024-07-22 (午餐 15:00 ~ 14:00)' 格式的时间，分离出日期
        String[] dateAndMealFlag = rawDate.split(" \\(");
        if (dateAndMealFlag.length != 2) {
            log.error("companyId: {}, rawDate: {}, current: {}, size: {}", companyId, rawDate, current, size);
            throw new IllegalArgumentException("时间格式不正确，请联系管理员排查");
        }
        String date = dateAndMealFlag[0];
        String meal = dateAndMealFlag[1].replace(")", "");
        LocalDate localDate = LocalDateTimeUtil.parseDate(date);
        Page<OrderDetailDTO> result = orderMapper.queryOrderDetailForAdmin(page, companyId, localDate, meal);

        // order_payment 表中没有 订单备注，此处存在问题，订单的备注信息应该数据订单，而不是 具体的某个菜品；此处从 order_detail 表中查询出某个订单的备注信息
        Map<String, String> orderIdToItemMap = evalOrderNoteForOrders(result.getRecords());
        // TODO: 订单名称规则？
        List<AdminOrderDetailVO> records = result.getRecords().stream().map(detail -> {
            AdminOrderDetailVO vo = new AdminOrderDetailVO(detail);
            String orderId = detail.getOrderId();
            List<OrderDetailStoreFoodDTO> foodDetailList = orderDetailMapper.selectByOrderId(orderId);
            String orderName = foodDetailList.stream().map(OrderDetailStoreFoodDTO::getFoodName).collect(Collectors.joining(","));
            vo.setOrderName(orderName);
            // 为导出的数据补充 订单备注 note
            if (orderIdToItemMap.containsKey(orderId)) {
                vo.setNote(orderIdToItemMap.get(orderId));
            }
            return vo;
        }).collect(Collectors.toList());
        return new PaginationResponseVO<>(result.getTotal(), result.getCurrent(), records);
    }

    private void addSheet(ExcelWriter writer, String sheetName, List<Map<String, Object>> data, boolean firstSheet) {
        if (firstSheet) {
            writer.renameSheet(sheetName);
        } else {
            writer.setSheet(sheetName);
        }
        writer.write(data, true);

        // 获取当前工作表
        Sheet sheet = writer.getSheet();

        // 设置“餐品”这一列的宽度，索引从 0 开始
        int dishNameColumnIndex = 3;
        sheet.setColumnWidth(dishNameColumnIndex, 40 * 256);

        // 设置“下单时间”这一列的宽度
        int orderTimeColumnIndex = 5;
        sheet.setColumnWidth(orderTimeColumnIndex, 20 * 256);
    }

    private Map<String, Object> createRow(Integer index, String username, String department, String dish, Double price, String storeName, Integer priceForMerchantInt, String orderTime, String note) {
        // 使用 LinkedHashMap 而不是 HashMap 防止标题栏乱序
        Map<String, Object> row = new LinkedHashMap<>();
        row.put("序号", index);
        row.put("用户名", username);
        row.put("部门", department);
        row.put("餐品", dish);
        row.put("给用户的销售价格", price);
        row.put("店铺", storeName);
        Double priceForMerchantDouble = priceForMerchantInt / 100.0;
        row.put("支付给店铺的费用", priceForMerchantDouble);
        row.put("下单时间", orderTime);
        row.put("备注", note);
        return row;
    }

    /**
     * 【1103新增功能】 查询出各个订单支付信息中，需要给商户结算相关的信息
     */
    private Map<String, StoreInfoForOrderDTO> evalOrderIdToMerchantInfoMap(List<AdminOrderDetailVO> orderDetailList) {

        List<String> orderIds = orderDetailList.stream().map(AdminOrderDetailVO::getOrderId).collect(Collectors.toList());
        // 【1103新增功能】 先某个公司的员工的所有订单的id，查询出该公司员工订单的 店铺相关信息，用来作为导出excel文件中，新增的【店铺名】，【支付给商家的费用】 两列的值
        Map<String, StoreInfoForOrderDTO> orderIdToVoMap = new HashMap<>();
        if (!CollectionUtils.isEmpty(orderIds)) {
            List<StoreInfoForOrderDTO> storeInfoForOrderDTOS = orderMapper.queryOrderStoreInfoByOrderIds(orderIds);
            // 将查询出的结果按照订单id进行聚合，key为订单id，value为订单中菜品来自那些店铺名称，需要给店铺支付的总金额；
            // todo 此处存在一个问题，当某个订单来自多个店铺时，sql查询结果对应的单个DTO中，店铺名称是多个店铺，而需要支付给商家的金额累加在一起，无法区分该订单应该为各个商家支付多少费用
            orderIdToVoMap = storeInfoForOrderDTOS.stream().collect((Collectors.toMap(StoreInfoForOrderDTO::getOrderId, Function.identity())));
        }

        return orderIdToVoMap;
    }

    private PaginationResponseVO<AdminOrderWithTenantNameVO> pageOrderForAdmin(String keyword, String tenantName, String startDateTime, String endDateTime, int current, int size) {
        Page<AdminOrderStatisticDTO> page = new Page<>(current, size);

        // 如果开始时间为空，则设置为最早日期
        if (startDateTime == null || startDateTime.isEmpty()) {
            startDateTime = "2024-01-01";
        }
        LocalDate startDate = LocalDateTimeUtil.parseDate(startDateTime);
        // 如果结束时间为空，则设置为当前日期的后一天；如果结束时间不为空，则将 yyyy-MM-dd 格式的时间转换成 LocalDate 类型
        LocalDate today = LocalDate.now();
        LocalDate endDate = today.plusDays(1);
        if (endDateTime != null && !endDateTime.isEmpty()) {
            endDate = LocalDateTimeUtil.parseDate(endDateTime);
        }

        Page<AdminOrderStatisticForAdminDTO> result = orderMapper.queryOrderStatisticForAdminByTenantName(page, keyword, tenantName, startDate, endDate);
        List<AdminOrderWithTenantNameVO> records = result.getRecords().stream()
                .map(AdminOrderWithTenantNameVO::new)
                .collect(Collectors.toList());

        return new PaginationResponseVO<>(result.getTotal(), result.getCurrent(), records);
    }

    private Map<String, String> evalOrderNoteForOrders(List<OrderDetailDTO> records) {

        List<String> orderIds = records.stream().map(OrderDetailDTO::getOrderId).distinct().collect(Collectors.toList());
        if (CollectionUtils.isEmpty(orderIds)) {
            return new HashMap<>();
        }
        List<OrderDetail> orderDetails = orderDetailMapper
                .selectList(new LambdaQueryWrapper<OrderDetail>()
                                .in(OrderDetail::getOrderId, orderIds));
        // 一个orderId在 order_detail 表中可能会对应多条记录，因为一份订单可能会有多份菜品，order_detail中多条记录的note都会有值，此处聚合成map时，当一个orderId有多条记录时，取其中一个note不为空的记录的note字段的值作为map的value
        // 理论上，order_detail表中，orderId相同的多条记录的note字段的值应该是完全一样的，或者都为null 或 空字符串，或者都有值
        Map<String, String> orderIdToNoteMap =
                orderDetails.stream().collect(
                        Collectors.toMap(
                                OrderDetail::getOrderId,
                                OrderDetail::getNote,
                                (order1Note, order2Note) -> StringUtils.hasText(order1Note) ? order1Note : order2Note)
                );

        return orderIdToNoteMap;
    }

}
